package cqueue;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import cqueue.firing.FireService;
import cqueue.posting.PostService;

/**
 * A nice generic event queue. 
 * 
 * Note: Adding event bubbling would involve creating a new
 * fire service.  So specializing this class to handle bubbling
 * would only involve parameterizing EventQueue once the service
 * is written.
 * 
 * @author Kristopher L. Kalish, James Madison University
 * @version 1 - March 24, 2008 (initial design)
 * @version 2 - March 29, 2008 (added multiple service capability & bubbling cap.)
 *
 * @param <E> The type of event this Queue should handle. We'll reuse this
 * later for KingEvents... or something like that.  Whatever we find 
 * appropriate.
 */

public abstract class EventQueue<E extends Event> implements Runnable
{
	protected volatile boolean   running;
	protected List<E>            events;
	protected Object             lock; // we need this to do thread.wait()
	protected Thread             thread;
	
	protected List<FireService<E>> fireServices;
	protected List<PostService<E>> postServices;
	
	/**
	 * Default constructor.
	 * Note: A queue will not function without services.
	 * use addFireService() and addPostService() to add
	 * functionality.
	 */
	public EventQueue()
	{
		events = Collections.synchronizedList(new ArrayList<E>());
		fireServices = new ArrayList<FireService<E>>();
		postServices = new ArrayList<PostService<E>>();
		lock = new Object();
	}
	
	/**
	 * Adds a firing service. Any number and combination 
	 * may be used to achieve varying functionality.
	 * 
	 * @param fireService A firing service.
	 */
	public void addFireService(FireService<E> fireService) 
	{
		fireServices.add(fireService);
	}
	
	/**
	 * Adds a posting service.
	 * 
	 * @param postService
	 */
	public void addPostService(PostService<E> postService)
	{
		postServices.add(postService);
	}
	
	/**
	 * Fires events as they come in. This method ends up running
	 * in its own thread of execution.
	 */
	public void run()
	{
		while(running)
		{
			synchronized(lock)
			{
				fireEvents();
				
				try {
					lock.wait();
				} catch (InterruptedException ioe) {
					// don't care
				}
			}
		}
	}
	
	/**
	 * Fires all of the remaining/pending events.
	 * 
	 * This directs the request through a FireProxy
	 */
	private void fireEvents()
	{		
		Iterator<E> i = events.iterator();
		
		while(i.hasNext())
		{
			E next = i.next(); // store for reuse on multiple fire services
			
			// fire using all firing services
			for(FireService<E> fireService : fireServices)
				fireService.fire(next);
			
			i.remove(); // remove last thing we took
		}
	}
	
	/**
	 * 
	 * @param event
	 */
	public void postEvent(E event)
	{
		synchronized(lock) {
			// post using all posting services
			for(PostService<E> postService : postServices)
				postService.postEvent(event);
			
			lock.notifyAll(); // stops the thread from waiting
		}
	}
	
	/**
	 * 
	 * @param fireService
	 * @return
	 */
	public boolean removeFireService(FireService<E> fireService)
	{
		return fireServices.remove(fireService);
	}
	
	/**
	 * 
	 * @param postService
	 * @return
	 */
	public boolean removePostService(PostService<E> postService)
	{
		return postServices.remove(postService);
	}
	
	/**
	 * Not to be confused with Runnable's start(), this 
	 * will create a thread and run the event queue.
	 */
	public void start()
	{
		if(thread == null)
		{
			running = true;
			thread  = new Thread(this);
			thread.start();
		}
	}
}
